/**
 * Copyright [2019] [LiBo/Alex of copyright liboware@gmail.com ]
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.hyts.callcenter.tool;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.Validate;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

import static org.apache.commons.lang3.reflect.MethodUtils.getMatchingAccessibleMethod;

/**
 * @project-name:wiz-sound-ai2
 * @package-name:com.wiz.soundai.task.utils
 * @author:LiBo/Alex
 * @create-date:2021-09-28 16:33
 * @copyright:libo-alex4java
 * @email:liboware@gmail.com
 * @description:
 */
public class MethodUtils {




    public byte[] test(){
        return new byte[11];

    }

    /**
     * 调用方法机制控制
     * @param object
     * @param forceAccess
     * @param methodName
     * @param args
     * @param parameterTypes
     * @return
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public static Object invokeMethod(Object object, boolean forceAccess, String methodName,
                                      Object[] args, Class<?>[] parameterTypes) throws NoSuchMethodException,
            IllegalAccessException, InvocationTargetException {
        parameterTypes = ArrayUtils.nullToEmpty(parameterTypes);
        args = ArrayUtils.nullToEmpty(args);
        Method method = null;
        String messagePrefix;
        if (forceAccess) {
            messagePrefix = "No such method: ";
            method = getMatchingMethod(object.getClass(), methodName, parameterTypes);
            if (method != null && !method.isAccessible()) {
                method.setAccessible(true);
            }
        } else {
            messagePrefix = "No such accessible method: ";
            method = getMatchingAccessibleMethod(object.getClass(), methodName, parameterTypes);
        }

        if (method == null) {
            throw new NoSuchMethodException(messagePrefix + methodName + "() on object: " + object.getClass().getName());
        } else {
            args = toVarArgs(method, args);
            Object result = method.invoke(object, args);
            return result;
        }
    }


    private static Object[] toVarArgs(Method method, Object[] args) {
        if (method.isVarArgs()) {
            Class<?>[] methodParameterTypes = method.getParameterTypes();
            args = getVarArgs(args, methodParameterTypes);
        }

        return args;
    }


    static Object[] getVarArgs(Object[] args, Class<?>[] methodParameterTypes) {
        if (args.length != methodParameterTypes.length || args[args.length - 1] != null && !args[args.length - 1].getClass().equals(methodParameterTypes[methodParameterTypes.length - 1])) {
            Object[] newArgs = new Object[methodParameterTypes.length];
            System.arraycopy(args, 0, newArgs, 0, methodParameterTypes.length - 1);
            Class<?> varArgComponentType = methodParameterTypes[methodParameterTypes.length - 1].getComponentType();
            int varArgLength = args.length - methodParameterTypes.length + 1;
            Object varArgsArray = Array.newInstance(ClassUtils.primitiveToWrapper(varArgComponentType), varArgLength);
            System.arraycopy(args, methodParameterTypes.length - 1, varArgsArray, 0, varArgLength);
            if (varArgComponentType.isPrimitive()) {
                varArgsArray = ArrayUtils.toPrimitive(varArgsArray);
            }

            newArgs[methodParameterTypes.length - 1] = varArgsArray;
            return newArgs;
        } else {
            return args;
        }
    }


    public static Method getMatchingMethod(Class<?> cls, String methodName, Class... parameterTypes) {
        Validate.notNull(cls, "cls", new Object[0]);
        Validate.notEmpty(methodName, "methodName", new Object[0]);
        List<Method> methods =  Arrays.stream(cls.getDeclaredMethods()).filter((methodx) -> {
            return methodx.getName().equals(methodName);
        }).collect(Collectors.toList());
//                ClassUtils.getAllSuperclasses(cls).stream().map(Class::getDeclaredMethods).flatMap(Arrays::stream).filter((methodx) -> {
//            return methodx.getName().equals(methodName);
//        }).forEach(methods::add);

        Iterator var4 = methods.iterator();
        Method method;
        do {
            if (!var4.hasNext()) {
                TreeMap<Integer, List<Method>> candidates = new TreeMap();
                methods.stream().filter((methodx) ->  {
                    return ClassUtils.isAssignable(parameterTypes, methodx.getParameterTypes(), true);
                })
                        .forEach((methodx) -> {
                    int distance = distance(parameterTypes, methodx.getParameterTypes());
                    List<Method> candidatesAtDistance = (List)candidates.computeIfAbsent(distance, (k) -> {
                        return new ArrayList();
                    });
                    candidatesAtDistance.add(methodx);
                });
                if (candidates.isEmpty()) {
                    return null;
                }

                List<Method> bestCandidates = (List)candidates.values().iterator().next();
                if (bestCandidates.size() == 1) {
                    return (Method)bestCandidates.get(0);
                }

                throw new IllegalStateException(String.format("Found multiple candidates for method %s on class %s : %s", methodName + (String)Arrays.stream(parameterTypes).map(String::valueOf).collect(Collectors.joining(",", "(", ")")), cls.getName(), bestCandidates.stream().map(Method::toString).collect(Collectors.joining(",", "[", "]"))));
            }

            method = (Method)var4.next();
        } while(!Arrays.deepEquals(method.getParameterTypes(), parameterTypes));

        return method;
    }


    private static int distance(Class<?>[] fromClassArray, Class<?>[] toClassArray) {
        int answer = 0;
        if (!ClassUtils.isAssignable(fromClassArray, toClassArray, true)) {
            return -1;
        } else {
            for(int offset = 0; offset < fromClassArray.length; ++offset) {
                Class<?> aClass = fromClassArray[offset];
                Class<?> toClass = toClassArray[offset];
                if (aClass != null && !aClass.equals(toClass)) {
                    if (ClassUtils.isAssignable(aClass, toClass, true) && !ClassUtils.isAssignable(aClass, toClass, false)) {
                        ++answer;
                    } else {
                        answer += 2;
                    }
                }
            }

            return answer;
        }
    }

}
